
import threading
import time
import redis

ONE_WEEK_IN_SECONDS = 7 * 86400
VOTE_SCORE = 432
ARTICLES_PER_PAGE = 25

conn = redis.Redis(host='localhost', port=6379, db=0)

'''
3.6 发布与订阅
发布消息函数publisher(n)：
1、参数n是发布几条消息；
2、先休眠1秒sleep(1)，让订阅者有时间连接服务器并监听消息；
3、发送完一条消息，休眠一秒，可以让消息一条一条的出现
'''
def publisher(n):
    time.sleep(1)
    for i in range(n):
        conn.publish('channel', i)
        time.sleep(1)

'''
发布与订阅函数run_pubsub()：
1）启动辅助线程（threading），用线程发送3条消息；
2）创建发布与订阅对象pubsub
3）订阅指定的频道'channel'；
4）打印监听的接收到的每条消息；
5）收到4条消息（count == 4）后，发退订通知。4条消息为：一条订阅通知和三条消息）；
6）收到退订通知（count == 5）后，不再接收消息，第5条消息为退订通知；
打印结果（一共是5条消息）：
{'type': 'subscribe', 'pattern': None, 'channel': 'channel', 'data': 1}
{'type': 'message', 'pattern': None, 'channel': 'channel', 'data': '0'}
{'type': 'message', 'pattern': None, 'channel': 'channel', 'data': '1'}
{'type': 'message', 'pattern': None, 'channel': 'channel', 'data': '2'}
{'type': 'unsubscribe', 'pattern': None, 'channel': 'channel', 'data': 0}
'''
def run_pubsub():
    threading.Thread(target=publisher, args=(3,)).start()
    pubsub = conn.pubsub()
    pubsub.subscribe(['channel'])
    count = 0
    for item in pubsub.listen():
        print(item)
        count += 1
        if count == 4:
            pubsub.unsubscribe()
        if count == 5:
            break
# 运行发布订阅
# run_pubsub()

'''
3.7.2 redis事务
没有事务函数，执行并行命令，会引发问题。redis使用管道（pipeline）控制事务，
先把命名放到pipeline中，调用execute()方法，才执行命令。
1）通过conn获取pipeline;
2）在管道中，对计数器'tran'自增；
3）等待100毫秒；
4）对计数器'notrans'自减，并打印结果
5）启动三个线程(if语句)执行函数trans()；
6）休眠0.5秒让，让函数trans()有足够时间完成；
7）如果没有管道的事务控制，打印的结果不可预期；
'''
def trans():
    pipeline = conn.pipeline()
    pipeline.incr('trans:')
    time.sleep(.1)
    pipeline.incr('trans:', -1)
    print(pipeline.execute()[0])
# if 1:
#      for i in range(3):
#          threading.Thread(target=trans).start()
#          time.sleep(.5)

# 对文章投票
def article_vote(conn, user, article):
    cutoff = time.time() - ONE_WEEK_IN_SECONDS
    posted = conn.zscore('time:', article)
    if posted < cutoff:
        return

    article_id = article.partition(':')[-1]
    pipeline = conn.pipeline()
    pipeline.sadd('voted:' + article_id, user)
    pipeline.expire('voted:' + article_id, int(posted-cutoff))
    if pipeline.execute()[0]:
        pipeline.zincrby('score:', article, VOTE_SCORE)
        pipeline.hincrby(article, 'votes', 1)
        pipeline.execute()

# 投票（支持事务）
def article_vote(conn, user, article):
    cutoff = time.time() - ONE_WEEK_IN_SECONDS
    posted = conn.zscore('time:', article)
    article_id = article.partition(':')[-1]
    voted = 'voted:' + article_id

    pipeline = conn.pipeline()
    while posted > cutoff:
        try:
            pipeline.watch(voted)
            if not pipeline.sismember(voted, user):
                pipeline.multi()
                pipeline.sadd(voted, user)
                pipeline.expire(voted, int(posted-cutoff))
                pipeline.zincrby('score:', article, VOTE_SCORE)
                pipeline.hincrby(article, 'votes', 1)
                pipeline.execute()
            else:
                pipeline.unwatch()
            return
        except redis.exceptions.WatchError:
            cutoff = time.time() - ONE_WEEK_IN_SECONDS

# 获取最新的文章
def get_articles(conn, page, order='score:'):
    start = max(page-1, 0) * ARTICLES_PER_PAGE
    end = start + ARTICLES_PER_PAGE - 1

    ids = conn.zrevrangebyscore(order, start, end)

    pipeline = conn.pipeline()
    all(map(pipeline.hgetall, ids))

    articles = []
    for id, article_data in zip(ids, pipeline.execute()):
        article_data['id'] = id
        articles.append(article_data)
    return articles

# 更新token
def update_token(conn, token, user, item=None):
    timestamp = time.time()
    conn.hset('login:', token, user)
    conn.zadd('recent:', token, timestamp)
    if item:
        key = 'viewed:' + token
        conn.lrem(key, item)
        conn.rpush(key, item)
        conn.ltrim(key, -25, -1)
        conn.zincrby('viewed:', item, -1)

# 校验token
THIRTY_DAYS = 30*86400
def check_token(conn, token):
    return conn.get('login:' + token)

# 跟新token
def update_token(conn, token, user, item=None):
    conn.setex('login:' + token, user, THIRTY_DAYS)
    key = 'viewed:' + token
    if item:
        conn.lrem(key, item)
        conn.rpush(key, item)
        conn.ltrim(key, -25, -1)
        conn.zincrby('viewed:', item, -1)
    conn.expire(key, THIRTY_DAYS)

# 添加到购物车
def add_to_cart(conn, session, item, count):
    key = 'cart:' + session
    if count <= 0:
        conn.hrem(key, item)
    else:
        conn.hset(key, item, count)
    conn.expire(key, THIRTY_DAYS)
